package com.piesat.quake.business.device.service.impl;

import com.piesat.quake.business.device.bis.DeviceCache;
import com.piesat.quake.business.device.bis.MqttHelper;
import com.piesat.quake.business.device.model.BaseModel;
import com.piesat.quake.business.device.model.ConfigRequest;
import com.piesat.quake.business.device.model.ConfigurationUpdate;
import com.piesat.quake.business.device.model.DeviceConfigVO;
import com.piesat.quake.business.device.model.DeviceLog;
import com.piesat.quake.business.device.model.DeviceOptions;
import com.piesat.quake.business.device.model.DeviceState;
import com.piesat.quake.business.device.model.DeviceStatus;
import com.piesat.quake.business.device.model.DeviceUpdate;
import com.piesat.quake.business.device.model.EventData;
import com.piesat.quake.business.device.model.FeatureRequest;
import com.piesat.quake.business.device.model.NetworkData;
import com.piesat.quake.business.device.model.RealtimeControl;
import com.piesat.quake.business.device.model.RegisterRequest;
import com.piesat.quake.business.device.model.RegisterResponse;
import com.piesat.quake.business.device.model.config.NtpData;
import com.piesat.quake.business.device.model.config.Passageway;
import com.piesat.quake.business.device.model.config.StationData;
import com.piesat.quake.business.device.model.config.ThresholdData;
import com.piesat.quake.business.device.mqtt.LengthData;
import com.piesat.quake.business.device.mqtt.factory.PipelineContext;
import com.piesat.quake.business.device.mqtt.pipeline.DataPipeline;
import com.piesat.quake.business.device.mqtt.pipeline.Pipeline;
import com.piesat.quake.business.device.service.DStationDataService;
import com.piesat.quake.business.device.service.DeviceService;
import com.piesat.quake.business.device.service.EventDataService;
import com.piesat.quake.business.mail.MailService;
import com.piesat.quake.business.station.model.StationInfo;
import com.piesat.quake.business.station.service.StationInfoService;
import com.piesat.quake.common.mongo.service.MongoService;
import com.piesat.quake.common.mqtt.MqttGateway;
import com.piesat.quake.common.mqtt.decoder.FdDataDecoder;
import com.piesat.quake.common.mqtt.decoder.WcDataDecoder;
import com.piesat.quake.common.mqtt.decoder.entity.FdData;
import com.piesat.quake.common.mqtt.decoder.entity.WcData;
import com.piesat.quake.common.mqtt.entity.MqttEvent;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

import javax.annotation.PostConstruct;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Gersy
 * @date 2023/2/13
 * @since 1.0
 */
@Slf4j
@Service
public class DeviceServiceImpl implements DeviceService, DeviceCache.ConnectionEvent {
    @Autowired
    private StationInfoService stationInfoService;
    @Autowired
    private DeviceCache deviceCache;
    @Autowired
    private MongoService mongoService;
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    @Autowired
    private MqttGateway mqttGateway;
    @Autowired
    private PipelineContext pipelineContext;
    @Autowired
    private DeviceCommonDataService deviceCommonDataService;
    @Autowired
    private DStationDataService stationDataService;
    @Autowired
    private EventDataService eventDataService;
    @Autowired
    private MailService mailService;

    @Lazy
    @PostConstruct
    private void init() {
        pipelineContext.loadAnnotationPipeline(new String[]{"com.piesat.quake.business.device.model"});
        registerNtpPipeline();
        registerPassagewayPipeline();
        registerThresholdPipeline();
        registerUpdateConfigPipeline();
        registerUpdateFtpConfigPipeline();
        registerUpdateFtpLogConfigPipeline();
        registerUpdateMqttConfigPipeline();
        registerUpdateStationInfoConfigPipeline();
        deviceCache.registerEvent(this);
        deviceCache.initCache(stationInfoService.list());
        MqttHelper.initVersionCache(stationDataService.list());
    }

    /**
     * 获取设备配置报文
     */
    @Override
    public void getConfig(String id) {
        ConfigRequest configRequest = new ConfigRequest();
        configRequest.setId(id);
        configRequest.createTimestamp();
        DataPipeline pipeline = pipelineContext.getPipeline(ConfigRequest.class);
        mqttGateway.sendToMqtt(MqttHelper.getTopic(id, MqttHelper.Outbound.DEVICE_CONFIG), 1, pipeline.encode(configRequest));
    }

    /**
     * 获取特征数据
     */
    @Override
    public void getFeatureData(String id, long timestamp) {
        DataPipeline pipeline = pipelineContext.getPipeline(FeatureRequest.class);
        mqttGateway.sendToMqtt(MqttHelper.getTopic(id, MqttHelper.Outbound.FEATURE_CONTROL), pipeline.encode(FeatureRequest.create(id, timestamp)));
    }

    @Override
    public void upgradeDevice(List<String> ids) {
        DataPipeline pipeline = pipelineContext.getPipeline(DeviceUpdate.class);
        DeviceUpdate deviceUpdate = new DeviceUpdate();
        deviceUpdate.createTimestamp();
        if (ids == null) {
            List<StationInfo> list = stationInfoService.list();
            for (StationInfo stationInfo : list) {
                deviceUpdate.setId(stationInfo.getId());
                mqttGateway.sendToMqtt(MqttHelper.getTopic(stationInfo.getId(), MqttHelper.Outbound.UPDATE_DEVICE), pipeline.encode(deviceUpdate));
            }
        } else {
            for (String id : ids) {
                deviceUpdate.setId(id);
                mqttGateway.sendToMqtt(MqttHelper.getTopic(id, MqttHelper.Outbound.UPDATE_DEVICE), pipeline.encode(deviceUpdate));
            }
        }
    }

    @Override
    public void updateConfig(ConfigurationUpdate configurationUpdate) {
        if (configurationUpdate.getTargetType() == null) {
            throw new IllegalStateException("请传入目标类型");
        }
        //批量修改则保存
        if (configurationUpdate.getTargetType() == 1) {
            deviceCommonDataService.updateConfig(configurationUpdate);
        }
        sendUpdateConfigMessage(configurationUpdate);
    }

    /**
     * 发送更新配置的报文
     */
    private void sendUpdateConfigMessage(ConfigurationUpdate configurationUpdate) {
        short configType = configurationUpdate.getConfigType();
        DataPipeline pipeline = null;
        switch (configType) {
            case ConfigurationUpdate.NETWORK:
                ConfigurationUpdate.Network network = configurationUpdate.getNetwork();
                byte communicationType = network.getCommunicationType();
                switch (communicationType) {
                    case ConfigurationUpdate.Network.MQTT:
                        pipeline = pipelineContext.getPipeline("updateMqttConfig");
                        break;
                    case ConfigurationUpdate.Network.FTP:
                        pipeline = pipelineContext.getPipeline("updateFtpConfig");
                        break;
                    case ConfigurationUpdate.Network.FTP_LOG:
                        pipeline = pipelineContext.getPipeline("updateFtpLogConfig");
                        break;
                    case ConfigurationUpdate.Network.UPDATE:
                        pipeline = pipelineContext.getPipeline("updateUpdateConfig");
                        break;
                }
                break;
            case ConfigurationUpdate.STATION_INFO:
                pipeline = pipelineContext.getPipeline("updateStationInfoConfig");
                break;
            case ConfigurationUpdate.THRESHOLD:
                pipeline = pipelineContext.getPipeline("updateThresholdConfig");
                break;
            case ConfigurationUpdate.PASSAGEWAY:
                pipeline = pipelineContext.getPipeline("updatePassagewayConfig");
                break;
            case ConfigurationUpdate.NTP:
                pipeline = pipelineContext.getPipeline("updateNtpConfig");
                break;
        }
        assert pipeline != null;
        if (configurationUpdate.getTargetType() == 0) {
            sendConfigUpdate(pipeline, configurationUpdate);
        } else {
            String regionCode = configurationUpdate.getId();
            List<String> deviceIds = stationInfoService.getDeviceIds(regionCode);
            for (String deviceId : deviceIds) {
                configurationUpdate.setId(deviceId);
                sendConfigUpdate(pipeline, configurationUpdate);
            }
        }
    }

    private void sendConfigUpdate(DataPipeline pipeline, ConfigurationUpdate configurationUpdate) {
        String deviceId = configurationUpdate.getId();
        mqttGateway.sendToMqtt(
                MqttHelper.getTopic(deviceId, MqttHelper.Outbound.UPDATE_CONFIG),
                1,
                pipeline.encode(configurationUpdate)
        );
        getConfig(deviceId);
    }

    @Override
    public DeviceConfigVO getDeviceConfig(String id) {
        return deviceCommonDataService.getDeviceFullConfig(id);
    }

    @Override
    public DeviceConfigVO getRegionDeviceConfig(String regionCode) {
        return deviceCommonDataService.getRegionDeviceConfig(regionCode);
    }

    @Override
    public void getDeviceLog(String id, Byte type) {
        DataPipeline pipeline = pipelineContext.getPipeline(DeviceLog.class);
        DeviceLog deviceLog = new DeviceLog();
        deviceLog.createTimestamp();
        deviceLog.setId(id);
        deviceLog.setNumber(type);
        mqttGateway.sendToMqtt(MqttHelper.getTopic(id, MqttHelper.Outbound.LOG), pipeline.encode(deviceLog));
    }

    @Override
    public void deviceUpdate(DeviceUpdate deviceUpdate) {
        DataPipeline pipeline = pipelineContext.getPipeline(DeviceUpdate.class);
        deviceUpdate.createTimestamp();
        mqttGateway.sendToMqtt(MqttHelper.getTopic(deviceUpdate.getId(), MqttHelper.Outbound.UPDATE_DEVICE), pipeline.encode(deviceUpdate));
    }

    /**
     * 实时控制
     */
    @Override
    public void realtimeControl(RealtimeControl realtimeControl) {
        DataPipeline pipeline = pipelineContext.getPipeline(RealtimeControl.class);
        realtimeControl.createTimestamp();
        mqttGateway.sendToMqtt(MqttHelper.getTopic(realtimeControl.getId(), MqttHelper.Outbound.REALTIME_CONTROL), pipeline.encode(realtimeControl));
    }

    /**
     * 注册报文
     */
    @EventListener(condition = "#mqttEvent.topic.contains('register/data')")
    public void onMqttCallRegister(MqttEvent mqttEvent) {
        StationData stationData = MqttHelper.setVersion(mqttEvent.getTopic());
        stationDataService.update(stationData);
        //解析注册报文，返回注册成功
        DataPipeline pipeline = pipelineContext.getPipeline(RegisterRequest.class);
        RegisterRequest data = (RegisterRequest) pipeline.decode(mqttEvent.getMessage());
        //调用注册消息表示设备的上线时间
        stationInfoService.updateDeviceTime(stationData.getId());
        DataPipeline responsePipeline = pipelineContext.getPipeline(RegisterResponse.class);
        //回复注册成功
        RegisterResponse response = RegisterResponse.create(data.getId(), RegisterResponse.SUCCESS);
        mqttGateway.sendToMqtt(MqttHelper.getTopic(data.getId(), MqttHelper.Outbound.REGISTER), 1, responsePipeline.encode(response));
    }

    @EventListener(condition = "#mqttEvent.topic.contains('status/data')")
    public void deviceStatus(MqttEvent mqttEvent) {
        DataPipeline pipeline = pipelineContext.getPipeline(DeviceStatus.class);
        DeviceStatus deviceStatus = (DeviceStatus) pipeline.decode(mqttEvent.getMessage());
        deviceCache.update(deviceStatus.getId(), deviceStatus);
    }

    /**
     * 地震事件报文
     */
    @EventListener(condition = "#mqttEvent.topic.contains('event/data')")
    public void onMqttCallEvent(MqttEvent mqttEvent) {
        DataPipeline pipeline = pipelineContext.getPipeline(EventData.class);
        EventData eventData = (EventData) pipeline.decode(mqttEvent.getMessage());
        eventDataService.save(eventData);
        mailService.sendEventMail(eventData);
        messagingTemplate.convertAndSend("/topic/ti", eventData);
    }

    /**
     * 地震事件报文
     */
    @EventListener(condition = "#mqttEvent.topic.contains('conf/data')")
    public void onMqttConfigEvent(MqttEvent mqttEvent) {
        DataPipeline pipeline = pipelineContext.getPipeline(DeviceOptions.class);
        DeviceOptions deviceOptions = (DeviceOptions) pipeline.decode(mqttEvent.getMessage());
        deviceCommonDataService.saveConfig(deviceOptions);
    }

    /**
     * mqtt服务器配置
     */
    private void registerUpdateMqttConfigPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ConfigurationUpdate.Network.class, "network")
                .addNode(NetworkData.class, "network", "mqttConfig");

        pipelineContext.register("updateMqttConfig", pipeline);
    }

    /**
     * ftp/ftp日志配置
     */
    private void registerUpdateFtpConfigPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ConfigurationUpdate.Network.class, "network")
                .addNode(NetworkData.class, "network", "ftpConfig");

        pipelineContext.register("updateFtpConfig", pipeline);
    }

    private void registerUpdateFtpLogConfigPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ConfigurationUpdate.Network.class, "network")
                .addNode(NetworkData.class, "network", "ftpLogConfig");

        pipelineContext.register("updateFtpLogConfig", pipeline);
    }

    /**
     * 更新服务器配置
     */
    private void registerUpdateConfigPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ConfigurationUpdate.Network.class, "network")
                .addNode(NetworkData.class, "network", "updateConfig");

        pipelineContext.register("updateUpdateConfig", pipeline);
    }

    private void registerUpdateStationInfoConfigPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(StationData.class, "stationData");

        pipelineContext.register("updateStationInfoConfig", pipeline);
    }

    /**
     * 事件检测模式与阈值调整配置
     */
    private void registerThresholdPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ThresholdData.class, "threshold");

        pipelineContext.register("updateThresholdConfig", pipeline);
    }

    /**
     * 通道配置调整
     */
    private void registerPassagewayPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(ConfigurationUpdate.PassagewayConfig.class, "passageway")
                .addNode(Passageway.class, "passageway", "passageway1")
                .addNode(Passageway.class, "passageway", "passageway2")
                .addNode(Passageway.class, "passageway", "passageway3")
                .addNode(Passageway.class, "passageway", "passageway4")
                .addNode(Passageway.class, "passageway", "passageway5")
                .addNode(Passageway.class, "passageway", "passageway6");

        pipelineContext.register("updatePassagewayConfig", pipeline);
    }

    /**
     * ntp
     */
    private void registerNtpPipeline() {
        DataPipeline pipeline = new Pipeline(ConfigurationUpdate.class)
                .addLast(BaseModel.class)
                .addLast(ConfigurationUpdate.class)
                .addNode(NtpData.class, "ntp");

        pipelineContext.register("updateNtpConfig", pipeline);
    }

    /**
     * 实时数据报文
     */
//    @EventListener(condition = "#mqttEvent.topic.endsWith('GZ.00032/ontime/data')")
    public void onMqttCallOnTime(MqttEvent mqttEvent) {
//        log.info("接收到实时数据报文：{}", mqttEvent.getMessage());
        WcDataDecoder decoder = new WcDataDecoder(mqttEvent.getMessage());
        WcData data = decoder.decoder();

//        DataPipeline pipeline = pipelineContext.getPipeline("realtimeData");
//        LengthData decode = pipeline.decode(mqttEvent.getMessage());
//        System.out.println(data);
//        template.convertAndSend("/topic/wc", data);
//        mongoService.save(data);
//        webSocket.sendAllTopicMessage("wc", JSONObject.toJSONString(data));
    }

    //    @EventListener(condition = "#mqttEvent.topic.endsWith('GZ.00032/feature/data')")
    public void onMqttCallFeature(MqttEvent mqttEvent) {
        byte[] message = mqttEvent.getMessage();
        FdDataDecoder decoder = new FdDataDecoder(message);
        FdData data = decoder.decoder();
        DataPipeline pipeline = pipelineContext.getPipeline("featureData");
        LengthData decode = pipeline.decode(mqttEvent.getMessage());
        System.out.println(data);
//        webSocket.sendAllTopicMessage("fd", JSONObject.toJSONString(data));
    }

    @Override
    public void onConnected(StationInfo station) {
        messagingTemplate.convertAndSend("/topic/onStateChange", new DeviceState(station.getId(), DeviceState.ON, station.getTemperature(), station.getDataTimestamp()));
        StationInfo stationInfo = new StationInfo();
        stationInfo.setId(stationInfo.getId());
        stationInfo.setOnline(true);
        stationInfoService.updateById(stationInfo);
    }

    @Override
    public void onDisconnected(String id) {
        messagingTemplate.convertAndSend("/topic/onStateChange", new DeviceState(id, DeviceState.OFF));
        StationInfo stationInfo = new StationInfo();
        stationInfo.setId(id);
        stationInfo.setOnline(false);
        stationInfoService.updateById(stationInfo);
    }
}
